﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using SKYPE4COMLib;
using System.Collections;

namespace SkypeConsole
{
    class CmdParser
    {
        private static Skype skype;
        private static GoogleSearch google;
        private static WolframAPI wolfram;
        private static MathParser mathp;
        private static Random random;
        private ArrayList aliases;

        public string    mem = "";
        public bool     scan = false;
        public string   resp = "";
        public int  cmdstack = 0;
        public string chatid = "";
        public bool     exec = true;

        public CmdParser(Skype s)
        {
            random = new Random();
            aliases = new ArrayList();

            google = new GoogleSearch();
            wolfram = new WolframAPI();
            mathp = new MathParser();

            skype = s;
            skype.MessageStatus += new _ISkypeEvents_MessageStatusEventHandler(skype_MessageStatus);
        }

        /*
         * DEFAULT
         * Skype event parser
         */
        public void skype_MessageStatus(ChatMessage pMessage, TChatMessageStatus Status)
        {
            // Queue
            if (Status == TChatMessageStatus.cmsSent && cmdstack > 0)
            {
                cmdstack--;
                return;
            }

            if (Status == TChatMessageStatus.cmsSent || Status == TChatMessageStatus.cmsReceived)
            {
                if (scan && pMessage.Chat.Name != chatid) return;
                
                string s_msg;

                // User input
                if (scan)
                {
                    resp = pMessage.Body;
                    s_msg = mem;
                }
                else s_msg = pMessage.Body + ";";

                // Split into commands
                s_msg = s_msg.Replace("\n", " ");
                s_msg = s_msg.Replace("\r", "");
                while (s_msg.Contains("  ")) s_msg = s_msg.Replace("  ", " ");
                s_msg = s_msg.Replace("; ", ";");
                s_msg = s_msg.Replace(" ;", ";");
                string[] commands = s_msg.Split(';');

                mem = s_msg;

                // Execute
                for (int i = 0; i < commands.Length; i++)
                {
                    // Whether it's command
                    if (commands[i].IndexOf('.') != 0) return;

                    // Assignment operator
                    string param = "";
                    if (commands[i].Contains("->") && exec)
                    {
                        int pos = commands[i].IndexOf("->") + 2;
                        param = commands[i].Substring(pos, commands[i].Length - pos);
                        commands[i] = commands[i].Substring(0, pos - 3);
                    }

                    // Launch functions
                    string reply = Launcher(commands[i]);
                    if (scan)
                    {
                        chatid = pMessage.Chat.Name;
                        return;
                    }

                    // Send result
                    if (param.Length > 0)
                    {
                        param = param.Trim();
                        if (param.IndexOf('%') != 0 || param.Contains(' '))
                        {
                            cmdstack++;
                            SendReply("CHATMESSAGE " + pMessage.Chat.Name + " Incompatible variable name.");
                        }
                        else AliasAdd(param, reply);
                    }
                    else
                    if (reply.Length > 0)
                    {
                        cmdstack++;
                        SendReply("CHATMESSAGE " + pMessage.Chat.Name + " " + reply);
                    }

                    // Cut queue
                    if (commands[i].Length + 1 <= s_msg.Length)
                        s_msg = s_msg.Substring(commands[i].Length + 1);
                    else s_msg = "";

                    // Backup
                    mem = s_msg;
                }

            }

        }

        /*
         * DEFAULT
         * Command sender
         */
        public void SendReply(string command)
        {
            Command cmd = new Command();
            cmd.Timeout = 200;
            cmd.Blocking = true;
            cmd.Command = command;
            skype.SendCommand(cmd);
        }

        /*
        * DEFAULT
        * Default commands lanucher
        */
        public string Launcher(string msg)
        {
            // Prepare variables
            string result = "";
            string[] command = msg.Split(' ');

            command[0] = ApplyAliases(command[0]);

            // Assume functions
            switch (command[0])
            {
                case ".date": result = _date(command); break;
                case ".calc": result = _calc(command); break;
                case ".set": result = _set(command); break;
                case ".out": result = _out(command); break;
                case ".clear": result = _clear(command); break;
                case ".in": result = _in(command); break;
                case ".wait": result = _wait(command); break;
                case ".invite": result = _invite(command); break;
                case ".sqrt": result = _sqrt(command); break;
                case ".google": result = _google(command); break;
                case ".rnd": result = _rnd(command); break;
                case ".wolfram": result = _wolfram(command); break;
                case ".help": result = _help(command); break;
                case ".if": result = _if(command); break;
                case ".endif": result = _endif(command); break;
                case "": break;
                default: result = "Unknown command: \"" + command[0] + "\""; break;
            }

            if (exec) return result;
            else return "";
        }
        
        /*
         * Flags parser
         */
        public bool InFlags(string flag, string flags)
        {
            if (flags.IndexOf("-") != 0) return false;
            if (flags.Contains(flag)) return true;
            else return false;
        }

        /*
         * ALIAS
         * Apply aliases
         */
        public string ApplyAliases(string source)
        {
            string msg = source;
            // Apply aliases
            for (int i = 0; i < aliases.Count; i++)
            {
                string[] temp = (string[])aliases[i];
                msg = msg.Replace(temp[0], temp[1]);
            }
            return msg;
        }

        /*
         * ALIAS
         * Alias position
         */
        public int AliasIndex(string alias)
        {
            for (int i = 0; i < aliases.Count; i++)
            {
                string[] temp = (string[])aliases[i];
                if (temp[0] == alias) return i;
            }

            return -1;
        }

        /*
         * ALIAS 
         * Add alias
         */
        public void AliasAdd(string arg0, string arg1)
        {
            int index = AliasIndex(arg0);
            string[] temp = { arg0, arg1 };

            if (index >= 0) aliases[index] = temp;
            else aliases.Add(temp);
        }

        /*
         * CMD
         * .date [-t|-s]
         */
        public string _date(string[] args)
        {
            if (args.Length > 1 && InFlags("t", args[1]))
                if (InFlags("s", args[1])) return DateTime.Now.ToShortTimeString();
                else return DateTime.Now.ToLongTimeString();
            else
                if (args.Length > 1 && InFlags("s", args[1])) return DateTime.Now.ToShortDateString();
                else return DateTime.Now.ToLongDateString();
        }
        
        /*
         * CMD
         * .calc [expression]
         */
        public string _calc(string[] args)
        {
            if (args.Length < 2) return "Using: .calc [exression]";

            if (args.Length > 2)
                for (int i = 2; i < args.Length; i++)
                    args[1] += " " + args[i];

            return mathp.Calculate(ApplyAliases(args[1])).ToString();
        }
    
        /*
         * CMD
         * .set %[key] [value]
         */
        public string _set(string[] args)
        {
            if (args.Length < 3 || args[1].IndexOf("%") != 0 || args[1].Length <= 1)
                return "Using: .set %[key] [value]";

            if (args.Length > 3)
                for (int i = 3; i < args.Length; i++)
                    args[2] += " " + args[i];

            AliasAdd(args[1], ApplyAliases(args[2]));

            return "";
        }

        /*
         * CMD
         * .wait [string]
         */
        public string _wait(string[] args)
        {
            if (args.Length < 2) return "Using: .wait [string]";

            if (!scan) scan = true;
            else
            {
                if (args.Length > 2)
                    for (int i = 2; i < args.Length; i++)
                        args[1] += " " + args[i];

                if (resp.Contains(args[1]))
                {
                    scan = false;
                    return resp;
                }
            }

            return "";
        }

        /*
         * CMD
         * .in %[key]
         */
        public string _in(string[] args)
        {
            if (args.Length < 2 || args[1].IndexOf("%") != 0 || args[1].Length <= 1)
                return "Using: .in %[key]";

            if (!scan) scan = true;
            else
            {
                scan = false;
                AliasAdd(args[1], resp);
            }
            
            return "";
        }

        /*
         * CMD
         * .out [string]
         */
        public string _out(string[] args)
        {
            string result = "";
            if (args.Length > 1) for (int i = 1; i < args.Length; i++)
                result += args[i] + " ";
            return ApplyAliases(result);
        }

        /*
         * CMD
         * .clear
         */
        public string _clear(string[] args)
        {
            string result = ""; 
            for (int i = 0; i < 5; i++) result += "~\n"; 
            return result;
        }

        /*
         * CMD
         * .invite [string...]
         */
        public string _invite(string[] args)
        {
            for (int i = 1; i < args.Length; i++)
                SendReply("SET USER " + ApplyAliases(args[i]) + " BUDDYSTATUS 2 Please authorize me");
            return "";
        }

        /*
         * CMD
         * .sqrt [number]
         */
        public string _sqrt(string[] args)
        {
            if (args.Length < 2)
                return "Using: .sqrt [number]";

            double number = double.Parse(ApplyAliases(args[1]));
            return Math.Sqrt(number).ToString();
        }

        /*
         * CMD
         * .google [query]
         */
        public string _google(string[] args)
        {
            if (args.Length < 2)
                return "Using: .google [query]";

            if (args.Length > 2)
                for (int i = 2; i < args.Length; i++)
                    args[1] += " " + args[i];

            string[] data = google.search(ApplyAliases(args[1]));

            string result = "Невозможно погуглить, наврное таймаут или ничего не найдено :(\nНе ссыте такое бывает o/";
            if (data.Length > 1)
            {
                result = data[1].Replace("<b>", "").Replace("</b>", "") + "\n";
                result += data[0] + "\n\n";
                result += data[2].Replace("<b>", "").Replace("</b>", "");
            }

            return result;
        }

        /*
         * CMD
         * .rnd [items...]
         */
        public string _rnd(string[] args)
        {
            if (args.Length < 2) return "Using: .rnd [items]"; 
            return args[random.Next(args.Length - 1) + 1];
        }

        /*
         * CMD
         * .wolfram [query]
         */
        public string _wolfram(string[] args)
        {
            if (args.Length < 2)
                return "Using: .wolfram [query]";

            if (args.Length > 2)
                for (int i = 2; i < args.Length; i++)
                    args[1] += " " + args[i];

            return wolfram.format(wolfram.query(ApplyAliases(args[1])));
        }

        /*
         * CMD
         * .if (-e) [op1] [s==|s!=|==|!=|<|>|<=|>=] [op2]
         */
        public string _if(string[] args)
        {
            string op1 = "";
            string op2 = "";
            int op = 0;
            int start = 1;

            // Else parsing
            if (InFlags("e", args[1]) && exec)
            {
                exec = false;
                return "";
            }
            else if (!InFlags("e", args[1]) && !exec) return "";

            if (InFlags("e", args[1])) start = 2;

            for (int i = start; i < args.Length; i++) 
            switch (args[i])
            {
                case "s==": op = 1; break;
                case "s!=": op = 2; break;
                case "@@":  op = 3; break;
                case "==":  op = 4; break;
                case "!=":  op = 5; break;
                case ">":   op = 6; break;
                case "<":   op = 7; break;
                case ">=":  op = 8; break;
                case "<=":  op = 9; break;
                default:    if (op == 0) op1 += ApplyAliases(args[i]) + " ";
                            else op2 += ApplyAliases(args[i]) + " "; break;
            }

            op1 = op1.Trim().ToLower();
            op2 = op2.Trim().ToLower();

            try
            {
                switch (op)
                {
                    case 0: exec = true; break;        // TRUE
                    case 1:                            // s==
                        if (op1 == op2) exec = true;
                        else exec = false;
                        break;
                    case 2:                            // s!=
                        if (op1 != op2) exec = true;
                        else exec = false;
                        break;
                    case 3:                            // @@
                        if (op2.Contains(op1)) exec = true;
                        else exec = false;
                        break;
                    case 4:                            // ==
                        if (double.Parse(op1) == double.Parse(op2)) exec = true;
                        else exec = false;
                        break;
                    case 5:                            // !=
                        if (double.Parse(op1) != double.Parse(op2)) exec = true;
                        else exec = false;
                        break;
                    case 6:                            // >
                        if (double.Parse(op1) > double.Parse(op2)) exec = true;
                        else exec = false;
                        break;
                    case 7:                            // <
                        if (double.Parse(op1) < double.Parse(op2)) exec = true;
                        else exec = false;
                        break;
                    case 8:                            // >=
                        if (double.Parse(op1) >= double.Parse(op2)) exec = true;
                        else exec = false;
                        break;
                    case 9:                            // <=
                        if (double.Parse(op1) <= double.Parse(op2)) exec = true;
                        else exec = false;
                        break;
                }
            }
            catch (Exception e) { return ""; }
            return "";
        }

        /*
         * CMD
         * .endif
         */
        public string _endif(string[] args)
        {
            exec = true;
            return "";
        }

        /*
         * CMD
         * .help
         */
        public string _help(string[] args)
        {
            return "Команды:\n" +
                   ".out - Стандартный вывод\n" +
                   ".in - Стандартный ввод\n" +
                   ".wait - Ожидание сообщения по шаблону\n" +
                   ".calc - Калькулятор\n" +
                   ".sqrt - Квадратный корень\n" +
                   ".date - Вывод даты/времени\n" +
                   ".set - Объявление переменных\n" +
                   ".invite - Добавить пользователей в список контактов\n" +
                   ".clear - Вывести отделяющее сообщение\n" +
                   ".help - Эта памятка\n" +
                   ".google - Поиск в гугл\n" +
                   ".rnd - Вывести случайное значение\n" +
                   ".wolfram - Запрос на Wolfram Alpha\n" +
                   ".if - Условие 'если'\n" +
                   ".endif - Завершение условия\n\n" +
                   "Операторы:\n" +
                   "[cmd] -> %[var] - Запись результата в переменную\n" +
                   "s==, s!=, @@ - Строковое сравнение\n" +
                   "==, !=, <, >, <=, >= - Числовое сравнение\n";
        }
   
    }
}
